package com.hx.hash;

import java.util.*;

/**
 * @author MinghuaChen
 * @date 2022-03-06 22:16
 * @description 一致性hash带虚拟节点
 */
public class ConsistentHashingWithVirtualNode {


    // 待加入hash环的服务器列表

    private static String[] servers = {
            "192.168.26.81",
            "192.168.26.82",
            "192.168.26.83",
            "192.168.26.84",
            "192.168.26.85",
    };

    // 真实节点列表
    private static List<String> realNodes = new LinkedList<>();

    // 虚拟节点列表
    private static SortedMap<Integer, String> virtualNodes = new TreeMap<>();

    // 设置一个真实节点对应5个虚拟节点
    private static final int VIRTUAL_NODES = 5;


    static {
        realNodes.addAll(Arrays.asList(servers));
        for (String realNode : realNodes) {
            for (int i = 0; i < VIRTUAL_NODES; i++) {
                String virtualNodeName = realNode + "&&VN" + i;
                int hash = getHash(virtualNodeName);
                System.out.println("虚拟节点[" + virtualNodeName + "]被添加, hash值为" + hash);
                virtualNodes.put(hash, virtualNodeName);
            }
        }
        System.out.println();
    }


    public static void main(String[] args) {
        String[] keys = {"太阳", "月亮", "星星"};
        for (int i = 0; i < keys.length; i++)
            System.out.println("[" + keys[i] + "]的hash值为" +
                    getHash(keys[i]) + ", 被路由到结点[" + getServer(keys[i]) + "]");

    }


    private static String getServer(String key) {
        int hash = getHash(key);
        SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
        String virtualNode;

        if (subMap.isEmpty()) {
            Integer integer = virtualNodes.firstKey();
            virtualNode = virtualNodes.get(integer);
        } else {
            Integer integer = subMap.firstKey();
            virtualNode = subMap.get(integer);
        }
        if (virtualNode != null || !"".equals(virtualNode.trim())) {
            return virtualNode.substring(0, virtualNode.indexOf("&&"));
        }
        return null;

    }


    /**
     * 获取hash值
     *
     * @param server 服务器地址
     * @return
     */
    private static int getHash(String server) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < server.length(); i++)
            hash = (hash ^ server.charAt(i)) * p;
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;

        // 如果算出来的值为负数则取其绝对值
        if (hash < 0)
            hash = Math.abs(hash);
        return hash;
    }

}
